package semantic.parser;

import error.processing.MyErrorProcessing;
import exp.parser.Exp;
import grammar.parser.FuncDef;
import grammar.parser.stmt.WhileStmt;
import word.parser.WordElement;

import java.util.*;

public class SemanticParser {
    private final Map<String, Stack<VariableSymbol>> variableSymbolTable = new HashMap<>();
    private final Map<String, Stack<FuncSymbol>> funcSymbolTable = new HashMap<>();
    private final Stack<String> variableStack = new Stack<>();
    private int stackFrame = 0;
    private int cycleLayerNum = 0;
    private Integer reVal = null;
    private final List<WhileStmt> cycleList = new ArrayList<>();
    private boolean callReturn = false;
    private boolean callBreakOrContinue = false;

    public SemanticParser() {
    }

    public void reset() {
        variableSymbolTable.clear();
        funcSymbolTable.clear();
        variableStack.clear();
        stackFrame = 0;
        cycleLayerNum = 0;
    }

    public SemanticParser(int stackFrame) {
        this.stackFrame = stackFrame;
    }

    public void declVariable(WordElement wordElement, SymbolTableElement.SymbolType type,
                             int scope, int dimension, List<Integer> lenOfDimension, List<Integer> value,
                             List<Exp> expValue) {
        assert Objects.equals(wordElement.getType(), WordElement.WordType.IDENFR);
        assert type == SymbolTableElement.SymbolType.CONST_INT ||
                type == SymbolTableElement.SymbolType.INT;
        String ident = wordElement.getWord();
        VariableSymbol element = new VariableSymbol(stackFrame, type, ident,
                scope, dimension, lenOfDimension, value, expValue);
        List<VariableSymbol> list = variableSymbolTable.computeIfAbsent(ident,
                k -> new Stack<>());
        if (list.contains(element)) {
            MyErrorProcessing.buildErrorProcessing(MyErrorProcessing.ErrorType.b, wordElement);
        } else {
            variableStack.push(ident);
            list.add(element);
        }
    }

    public void declVariable(String ident, SymbolTableElement.SymbolType type,
                             int scope, int dimension, List<Integer> lenOfDimension,
                             List<Integer> value) {
        VariableSymbol element = new VariableSymbol(stackFrame, type, ident,
                scope, dimension, lenOfDimension, value, new ArrayList<>());
        List<VariableSymbol> list = variableSymbolTable.computeIfAbsent(ident,
                k -> new Stack<>());
        variableStack.push(ident);
        list.add(element);
    }

    public void declVariable(String ident,
                             int scope, int dimension, List<Integer> lenOfDimension,
                             List<List<Integer>> value) {
        VariableSymbol element = new VariableSymbol(ident, scope, dimension, lenOfDimension, value);
        List<VariableSymbol> list = variableSymbolTable.computeIfAbsent(ident,
                k -> new Stack<>());
        variableStack.push(ident);
        list.add(element);
    }

    public void declFunc(WordElement wordElement, SymbolTableElement.SymbolType type, int address, FuncDef funcDef) {
        assert type == SymbolTableElement.SymbolType.VOID_FUNC || type == SymbolTableElement.SymbolType.INT_FUNC;
        assert wordElement.getType() == WordElement.WordType.IDENFR;
        String ident = wordElement.getWord();
        FuncSymbol element = new FuncSymbol(address, type, ident, funcDef);
        List<FuncSymbol> list =
                funcSymbolTable.computeIfAbsent(ident, k -> new Stack<>());
        if (list.contains(element)) {
            MyErrorProcessing.buildErrorProcessing(MyErrorProcessing.ErrorType.b, wordElement);
        } else {
            list.add(element);
        }
    }

    public void declFunc(FuncDef funcDef, int address) {
        String ident = funcDef.getIdent();
        SymbolTableElement.SymbolType type = funcDef.getFuncType();
        FuncSymbol element = new FuncSymbol(address, type, ident, funcDef);
        List<FuncSymbol> list =
                funcSymbolTable.computeIfAbsent(ident, k -> new Stack<>());
        list.add(element);
    }

    public void outScope(int scope) {
        do {
            if (variableStack.size() < 1) {
                break;
            }
            String stackTop = variableStack.lastElement();
            Stack<VariableSymbol> targetVector = variableSymbolTable.get(stackTop);
            if (targetVector.size() < 1) {
                break;
            }
            VariableSymbol element = targetVector.lastElement();
            assert element.getScope() <= scope;
            if (element.getScope() == scope) {
                targetVector.pop();
                variableStack.pop();
            } else {
                break;
            }
        } while (true);
    }

    public SymbolTableElement getVariableData(String ident) {
        Stack<VariableSymbol> stack = variableSymbolTable.get(ident);
        if (stack == null) {
            return null;
        }
        return stack.lastElement();
    }

    public int getCycleLayerNum() {
        return cycleLayerNum;
    }

    public void addCycleLayerNum() {
        cycleLayerNum++;
    }

    public void addCycleLayerNum(WhileStmt whileStmt) {
        cycleList.add(whileStmt);
    }

    public void outCycle() {
        assert cycleLayerNum > 0;
        cycleLayerNum--;
    }

    public void outCycle(WhileStmt whileStmt) {
        cycleList.remove(whileStmt);
    }

    public FuncSymbol getFunc(String ident) {
        if (funcSymbolTable.get(ident) == null ||
                funcSymbolTable.get(ident).size() < 1) {
            return null;
        }
        return funcSymbolTable.get(ident).lastElement();
    }

    public VariableSymbol getVariable(String ident) {
        if (variableSymbolTable.get(ident) == null ||
                variableSymbolTable.get(ident).size() < 1) {
            return null;
        }
        return variableSymbolTable.get(ident).lastElement();
    }

    public void setReVal(Integer reVal) {
        this.reVal = reVal;
    }

    public Integer getReVal() {
        return reVal;
    }

    public WhileStmt getLastCycle() {
        return cycleList.get(cycleList.size() - 1);
    }

    public boolean isCallReturn() {
        return callReturn;
    }

    public void callReturn() {
        this.callReturn = true;
    }

    public void finishReturn() {
        this.callReturn = false;
    }

    public void callBreakOrContinue() {
        callBreakOrContinue = true;
    }

    public void resetBreakOrContinue() {
        callBreakOrContinue = false;
    }

    public boolean isCallBreakOrContinue() {
        return callBreakOrContinue;
    }
}
